package com.GraduationDesign.backend.blImpl.codeReview;

import com.GraduationDesign.backend.bl.codeQuality.CodeQualityService;
import com.GraduationDesign.backend.bl.codeReview.ReviewService;
import com.GraduationDesign.backend.data.codeReview.ReviewMapper;
import com.GraduationDesign.backend.data.diff.DiffMapper;
import com.GraduationDesign.backend.data.project.ProjectMapper;
import com.GraduationDesign.backend.data.user.AccountMapper;
import com.GraduationDesign.backend.enums.*;
import com.GraduationDesign.backend.po.*;
import com.GraduationDesign.backend.utils.RequestUtil;
import com.GraduationDesign.backend.vo.*;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.lettuce.core.output.MapOutput;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.util.*;



/**
 * @author: wzx
 * @date: 2022/3/6
 */
@Service
@Slf4j
public class ReviewServiceImpl implements ReviewService {
    @Autowired
    ReviewMapper reviewMapper;

    @Autowired
    AccountMapper accountMapper;

    @Autowired
    ProjectMapper projectMapper;

    @Autowired
    DiffMapper diffMapper;

    @Autowired
    RedisTemplate<String,Object> redisTemplate;

    @Autowired
    RestTemplate restTemplate;

    @Autowired
    CodeQualityService codeQualityService;

    private final static String CALCULATE_URL="http://172.31.105.39:8000/api/diff/calculate";
//    private final static String CALCULATE_URL="http://172.31.105.39:9010/diff/calculate";


    @Override
    public ResponseVO addReview(ReviewVO reviewVO) {
        Review review=new Review();
        BeanUtils.copyProperties(reviewVO,review);
        ReviewStatus[] arr1=ReviewStatus.values();
        review.setStatus(arr1[reviewVO.getStatus()].toString());
        ResultStatus[] arr2=ResultStatus.values();
        review.setReviewResult(arr2[reviewVO.getReviewResult()].toString());
        reviewMapper.addReview(review);
        redisTemplate.opsForValue().set(review.getId().toString(),review);
        System.out.println(review.getId());
        Project project=projectMapper.getProjectInfo(review.getProgramId());
        PRInfo prInfo=new PRInfo(project.getRepositoryOwner(),project.getRepositoryName(),review.getPullRequestId());
        calculate(prInfo,review.getId());
        codeQualityService.calculateCodeQuality(prInfo, review.getId());
        reviewMapper.updateReviewStatus(review.getId(),ReviewStatus.WaitingGroup);
        return ResponseVO.buildSuccess(true);
    }

    @Override
    public ResponseVO deleteReview(int reviewId) {
        Review review=reviewMapper.getReview(reviewId);
        redisTemplate.delete(String.valueOf(reviewId));
        diffMapper.deleteBlocks(reviewId);
        diffMapper.deleteLinks(reviewId);
        diffMapper.deleteComments(reviewId);
        reviewMapper.deleteReview(reviewId);
        return ResponseVO.buildSuccess(true);
    }

    @Override
    public List<ReviewInfo> getReviews(Integer userId){
        UserType type= accountMapper.getUserType(userId);
        if(type==UserType.Coder){
            return DealWithReviewList(reviewMapper.getCoderReviews(userId));
        }
        else if(type==UserType.Reviewer){
            return DealWithReviewList(reviewMapper.getReviewerReviews(userId));
        }
        return null;
    }

    @Override
    public List<Reviewer> getReviewerList() {
        return  reviewMapper.getReviewerList();
    }

    @Override
    public List<Object> recentReviews(Integer userId){
        UserType type= accountMapper.getUserType(userId);
        if(type==UserType.Coder){
            List<Review> reviews=reviewMapper.getCoderReviews(userId);
            Set<String> keys =new HashSet<>();
            for(Review review:reviews){
                keys.add(review.getId().toString());
            }
            return redisTemplate.opsForValue().multiGet(keys);
        }
        else if(type==UserType.Reviewer){
            List<Review> reviews=reviewMapper.getReviewerReviews(userId);
            Set<String> keys =new HashSet<>();
            for(Review review:reviews){
                keys.add(review.getId().toString());
            }
            return redisTemplate.opsForValue().multiGet(keys);
        }
        return null;


    }

    @Override
    public ResponseVO addReviewContent(ReviewCommentVO reviewCommentVO) {
        ResultStatus resultStatus=null;
        if(reviewCommentVO.getResult()){
            resultStatus=ResultStatus.Pass;
        }else{
            resultStatus=ResultStatus.NoPass;
        }
        reviewMapper.updateReview(reviewCommentVO.getReviewId(),reviewCommentVO.getDescription(),resultStatus,ReviewStatus.Finished);
        return ResponseVO.buildSuccess("评论添加成功");
    }

    @Override
    public List<ReviewInfo> getReviewListByProgramId(Integer prgramid) {
        return DealWithReviewList(reviewMapper.getReviewListByProgramId(prgramid));
    }
    public List<ReviewInfo> DealWithReviewList(List<Review> reviewList){
        List<ReviewInfo> reviewInfoList=new ArrayList<>();
        for(Review review:reviewList){
            ReviewInfo reviewInfo=new ReviewInfo();
            BeanUtils.copyProperties(review,reviewInfo);
            reviewInfo.setApplicantName(accountMapper.getUserName(review.getApplicantId()));
            reviewInfo.setReviewerName(accountMapper.getUserName(review.getReviewerId()));
            reviewInfo.setProgramName(projectMapper.getProjectName(review.getProgramId()));
            reviewInfoList.add(reviewInfo);
        }
        return reviewInfoList;
    }
    @Override
    public DiffResponse calculate(PRInfo prInfo, Integer reiviewId) {
        HttpEntity<PRInfo> httpEntity=new HttpEntity<>(prInfo);
        ResponseEntity<String> responseEntity=null;
        DiffResponse diffResponse =null;
        try{
            responseEntity= RequestUtil.post(CALCULATE_URL, httpEntity);
            JSONObject jsonObject=JSONObject.parseObject(responseEntity.getBody());
            diffResponse = JSON.parseObject(String.valueOf(jsonObject), DiffResponse.class);
        }catch (RestClientException e){
            e.printStackTrace();
        }
        Calculate calculate=diffResponse.getData();
        Persistence(calculate,reiviewId);
        return diffResponse;
    }

    public void Persistence(Calculate calculate,Integer reviewId){
        //以differences为基准开始找起
        List<Diff> diffList=calculate.getDifferences();
        List<Group> groupList=calculate.getGroups();
        groupList = preHandleGroups(groupList);
        List<Link> linkList=calculate.getLinks();
        //存储link信息
        Map<String, List<String>> unMatchedGroup = new HashMap<>();
//        List<DiffInfo> unMatchedUpdate = new ArrayList<>();
        Map<DiffInfo, String> unMatchedUpdate = new HashMap<>();
        int maxCodeId = 0;
        for (Group group: groupList){
            List<String> gs = new ArrayList<>(group.getGroup());
            unMatchedGroup.put(group.getId(), gs);
        }
        for (Link link : linkList) {
            TempLink templink = new TempLink();
            templink.setCodeId(link.getFromId());
            templink.setReviewId(reviewId);
            templink.setDescription(link.getDescription());
            templink.setToId(link.getToId());
            diffMapper.addLink(templink);
        }
        for(Diff diff:diffList){
            //每一个文件
            String fileName= diff.getFilename();
            //每一个文件对应的diff
            List<DiffInfo> diffInfoList=diff.getDifferences();
            for(DiffInfo diffInfo:diffInfoList) {
                if (diffInfo.getId() >= maxCodeId){
                    maxCodeId = diffInfo.getId();
                }
                BlockInfo blockInfo = new BlockInfo();
                blockInfo.setCodeId(diffInfo.getId());
                blockInfo.setReviewId(reviewId);
                blockInfo.setDescription(diffInfo.getDescription());
                if (diffInfo.getDescription().startsWith("move")){
                    if (!diffInfo.getDescription().startsWith("moveExpression")){
                        continue;
                    }
                }
                //处理diffInfo中的range
                //TODO
                String[] range = diffInfo.getRange().split("-");
                if(range.length==1) {
                    StoreAddOrDeleteBlock(blockInfo,0,range,fileName,groupList,diffInfo, unMatchedGroup);
                }
                else if(range.length==2){
                    if("".equals(range[0])){
                        StoreUpdateBlock(blockInfo,1,range,fileName,groupList, ChangeFileType.New, unMatchedGroup);
                    }else{
                        BlockInfo blockInfo1=new BlockInfo();
                        BeanUtils.copyProperties(blockInfo,blockInfo1);
//                        StoreUpdateBlock(blockInfo,0,range,fileName,groupList,ChangeFileType.Old, unMatchedGroup);
//                        StoreUpdateBlock(blockInfo,1,range,fileName,groupList,ChangeFileType.New, unMatchedGroup);
                        StoreUpdateBlock(blockInfo, range, fileName, groupList, unMatchedGroup, diffInfo, unMatchedUpdate);
                    }
                }

            }
        }
        maxCodeId += handleUnMatchedUpdate(unMatchedGroup, unMatchedUpdate, reviewId);
//        Map<String, String> allLines = new HashMap<>();
//        unMatchedGroup.forEach((key, value) -> value.forEach(e -> allLines.put(e, key)));
        for (Map.Entry<String, List<String>> entry: unMatchedGroup.entrySet()){
            if (entry.getValue().size()!=0){
                for (String g: entry.getValue()){
                    maxCodeId++;
                    BlockInfo blockInfo = new BlockInfo();
                    blockInfo.setReviewId(reviewId);
                    blockInfo.setCodeId(maxCodeId);
                    blockInfo.setGroupId(Integer.parseInt(entry.getKey()));
                    String[] data = g.split(",");
                    String fullFilename = data[0];
                    String beginNumber = data[1];
                    String endNumber = data[2];
                    String tag = data[data.length-1].toLowerCase(Locale.ROOT);
                    String[] pathData = fullFilename.split("/");
                    int pos = 5;
                    for (int i = 0; i < 5; i++) {
                        pos += pathData[i].length();
                    }
                    blockInfo.setFilePath(fullFilename.substring(pos));
                    ChangeType changeType = findType(tag);
                    blockInfo.setType(changeType);
                    if (!"update".equals(changeType.name())){
                        blockInfo.setBeginLine(Integer.parseInt(beginNumber));
                        blockInfo.setEndLine(Integer.parseInt(endNumber));
                        String des = tag + " line";
                        if (!beginNumber.equals(endNumber)){
                            des = tag + " code block";
                        }
                        blockInfo.setDescription(des);
                        blockInfo.setFileType(findChangeType(changeType));
                        diffMapper.addBlock(blockInfo);
                    }else {
                        int[][] numbers = getLineRangeForUpdate(g);
                        blockInfo.setBeginLine(numbers[0][0]);
                        blockInfo.setEndLine(numbers[0][1]);
                        blockInfo.setFileType(ChangeFileType.Old);
                        String des = tag + " line";
                        if (! (numbers[0][0]==numbers[0][1])){
                            des = tag + " code block";
                        }
                        blockInfo.setDescription(des);
                        diffMapper.addBlock(blockInfo);

                        blockInfo.setBeginLine(numbers[1][0]);
                        blockInfo.setEndLine(numbers[1][1]);
                        blockInfo.setFileType(ChangeFileType.New);
                        diffMapper.addBlock(blockInfo);
                    }

                }
            }
        }
    }

    private int handleUnMatchedUpdate(Map<String, List<String>> unMatchedGroup, Map<DiffInfo, String> unMatchedUpdate, Integer reviewId) {
//        for (DiffInfo diff: unMatchedUpdate){
        int number = 0;
        Map<String, List<String>> tempGroup = new HashMap<>();
        for (Map.Entry<String, List<String>> entry: unMatchedGroup.entrySet()){
            tempGroup.put(entry.getKey(), new ArrayList<>(entry.getValue()));
        }
        for (Map.Entry<String, List<String>> entry: tempGroup.entrySet()){
            for (String line: entry.getValue()) {
                for (DiffInfo diff : unMatchedUpdate.keySet()) {
                    String[] data = line.split(",");
                    String[] pathData = data[0].split("/");
                    String fullFilename = data[0];
                    int pos = 5;
                    for (int i = 0; i < 5; i++) {
                        pos += pathData[i].length();
                    }
                    String filename = fullFilename.substring(pos);
                    if (!unMatchedUpdate.get(diff).equals(filename)){
                        continue;
                    }
                    String[] range = diff.getRange().split("-");
                    int[] rangeInDiffBefore = lineNumber(range[0]);
                    int[] rangeInDiffAfter = lineNumber(range[1]);
                    BlockInfo blockInfo = new BlockInfo();
                    blockInfo.setReviewId(reviewId);
                    blockInfo.setCodeId(diff.getId());
                    blockInfo.setGroupId(Integer.parseInt(entry.getKey()));
                    blockInfo.setFilePath(filename);
                    blockInfo.setDescription(diff.getDescription());
                    blockInfo.setType(ChangeType.update);
                    String tag = data[data.length - 1].toLowerCase(Locale.ROOT);
                    if ("update".equals(tag)) {
                        int[][] rangeInGroup = getLineRangeForUpdate(line);
                        if (rangeInDiffBefore[0] <= rangeInGroup[0][0] && rangeInDiffBefore[1] >= rangeInGroup[0][1]
                                && rangeInDiffAfter[0] <= rangeInGroup[1][0] && rangeInDiffAfter[1] >= rangeInGroup[1][1]) {
                            blockInfo.setBeginLine(rangeInDiffAfter[0]);
                            blockInfo.setEndLine(rangeInDiffAfter[1]);
                            blockInfo.setFileType(ChangeFileType.New);
                            diffMapper.addBlock(blockInfo);

                            blockInfo.setBeginLine(rangeInDiffBefore[0]);
                            blockInfo.setEndLine(rangeInDiffBefore[1]);
                            blockInfo.setFileType(ChangeFileType.Old);
                            diffMapper.addBlock(blockInfo);

                            List<String> group = unMatchedGroup.get(entry.getKey());
                            group.remove(line);
                            unMatchedGroup.put(entry.getKey(), group);
                            number += 2;
                        }
                    } else {
                        int beginNum = Integer.parseInt(data[1]);
                        int endNum = Integer.parseInt(data[2]);
                        if ("add".equals(tag)) {
                            if (rangeInDiffAfter[0] <= beginNum && rangeInDiffAfter[1] >= endNum) {
                                blockInfo.setBeginLine(rangeInDiffAfter[0]);
                                blockInfo.setEndLine(rangeInDiffAfter[1]);
                                blockInfo.setFileType(ChangeFileType.New);
                                diffMapper.addBlock(blockInfo);

                                blockInfo.setBeginLine(rangeInDiffBefore[0]);
                                blockInfo.setEndLine(rangeInDiffBefore[1]);
                                blockInfo.setFileType(ChangeFileType.Old);
                                diffMapper.addBlock(blockInfo);

                                List<String> group = unMatchedGroup.get(entry.getKey());
                                group.remove(line);
                                unMatchedGroup.put(entry.getKey(), group);
                                number += 2;

                            }
                        } else {
                            if (rangeInDiffAfter[0] <= beginNum && rangeInDiffAfter[1] >= endNum) {
                                blockInfo.setBeginLine(rangeInDiffAfter[0]);
                                blockInfo.setEndLine(rangeInDiffAfter[1]);
                                blockInfo.setFileType(ChangeFileType.New);
                                diffMapper.addBlock(blockInfo);

                                blockInfo.setBeginLine(rangeInDiffBefore[0]);
                                blockInfo.setEndLine(rangeInDiffBefore[1]);
                                blockInfo.setFileType(ChangeFileType.Old);
                                diffMapper.addBlock(blockInfo);

                                List<String> group = unMatchedGroup.get(entry.getKey());
                                group.remove(line);
                                unMatchedGroup.put(entry.getKey(), group);
                                number += 2;
                            }
                        }
                    }
                }
            }
        }
//        }
        return number;
    }

    private List<Group> preHandleGroups(List<Group> groupList) {
        List<Group> newGroup = new ArrayList<>();
        for (Group group: groupList){
            List<String> lines = new ArrayList<>(group.getGroup());
            for (String line: group.getGroup()){
                if (line.contains("/prev/") && !line.endsWith("UPDATE")){
                    String findLine = line.replace("/prev/", "/curr/").replace(",DELETE",",ADD");
                    if (lines.contains(findLine)){
                        lines.remove(findLine);
                        lines.remove(line);
                    }
                }
            }
            newGroup.add(new Group(group.getId(), new ArrayList<>(lines)));
        }
        return newGroup;
    }

    public static int[] lineNumber(String range){
        int[] lines=new int[2];
        String[] res=range.split(",");
        lines[0]=Integer.parseInt(res[0].substring(1));
        lines[1]=Integer.parseInt(res[1].substring(0,res[1].length()-1));
        return lines;
    }
    public boolean findGroupAndPath(BlockInfo blockInfo,List<Group> groupList,String fileAndRange, Map<String, List<String>> unMatched, ChangeType type, String[] range) {
        for (Group group : groupList) {
            for (String path : group.getGroup()) {
                if (checkContains(blockInfo, path, fileAndRange, type, range)) {
                    blockInfo.setGroupId(Integer.parseInt(group.getId()));
                    blockInfo.setFilePath(fileAndRange.split(",")[0]);
                    List<String> g = unMatched.get(group.getId());
                    g.remove(path);
                    unMatched.put(group.getId(), g);
                    return true;
                }
            }
        }
        return false;
    }


    public static int[][] getLineRangeForUpdate(String path){
        String[] change = path.split("\\|");
        String before = change[0];
        String[] beforeData = before.split(",");
        String after = change[1];
        String[] afterData = after.split(",");
        int[][] result = new int[2][2];
        result[0][0] = Integer.parseInt(beforeData[1]);
        result[0][1] = Integer.parseInt(beforeData[2]);
        result[1][0] = Integer.parseInt(afterData[1]);
        result[1][1] = Integer.parseInt(afterData[1]);
        return result;
    }

    private static boolean checkContains(BlockInfo blockInfo,String path, String fileAndRange, ChangeType type, String[] range){
        String[] data = path.split(",");
//        int beginLine = Integer.parseInt(data[1]);
//        int endLine = Integer.parseInt(data[2]);
        String fullFilename = data[0];
        String op = data[data.length-1].toLowerCase(Locale.ROOT);
        String[] paths = fullFilename.split("/");
        int len = 0;
        for (int i = 0; i < 5; i++) {
            len += paths[i].length();
        }
        String filenameInGroup = fullFilename.substring(len + 5);
        String filenameInDiff = fileAndRange.split(",")[0];
        if (filenameInDiff.equals(filenameInGroup)){
            switch (type.name()){
                case "add":
                case "delete":
                    int beginLine = Integer.parseInt(data[1]);
                    int endLine = Integer.parseInt(data[2]);
                    if (blockInfo.getEndLine()==endLine && blockInfo.getBeginLine()==beginLine
                            && type.name().equals(op)){
                        return true;
                    }
                    break;
                default:
                    if (!type.name().equals(op)){
                        return false;
                    }
                    int[] rangeInDiffBefore = lineNumber(range[0]);
                    int[] rangeInDiffAfter = lineNumber(range[1]);
                    int[][] rangeInGroup = getLineRangeForUpdate(path);
                    if (rangeInDiffBefore[0] == rangeInGroup[0][0] && rangeInDiffBefore[1] == rangeInGroup[0][1]
                            && rangeInDiffAfter[0] == rangeInGroup[1][0] && rangeInDiffAfter[1] == rangeInGroup[1][1]){
                        return true;
                    }
            }
        }
        return false;
    }

    public ChangeType findType(String description){
        if(description.contains("add")){
            return ChangeType.add;
        }
        else if(description.contains("delete")){
            return ChangeType.delete;
        }
        return ChangeType.update;
    }
    public ChangeFileType findChangeType(ChangeType changeType) {
        if (changeType == ChangeType.add) {
            return ChangeFileType.New;
        } else if (changeType == ChangeType.delete) {
            return ChangeFileType.Old;
        }
        return ChangeFileType.Both;
    }
    public void StoreAddOrDeleteBlock(BlockInfo blockInfo,Integer i,String[] range,String fileName,List<Group> groupList,DiffInfo diffInfo, Map<String, List<String>> unMatched){
        Integer first = lineNumber(range[i])[0];
        Integer second = lineNumber(range[i])[1];
        blockInfo.setBeginLine(first);
        blockInfo.setEndLine(second);
        String findRange = fileName + "," + first + "," + second;
        ChangeType changeType=findType(diffInfo.getDescription());
        if (diffInfo.getDescription().startsWith("update")){
            changeType = ChangeType.add;
        }
        boolean flag=findGroupAndPath(blockInfo,groupList,findRange, unMatched, changeType, range);
        blockInfo.setType(changeType);
        ChangeFileType changeFileType=findChangeType(changeType);
        blockInfo.setFileType(changeFileType);
        if(flag){
            diffMapper.addBlock(blockInfo);
        }

    }
    public void StoreUpdateBlock(BlockInfo blockInfo,Integer i,String[] range,String fileName,List<Group> groupList,ChangeFileType changeFileType, Map<String, List<String>> unMatched){
        Integer first = lineNumber(range[i])[0];
        Integer second = lineNumber(range[i])[1];
        blockInfo.setBeginLine(first);
        blockInfo.setEndLine(second);
        String findRange = fileName + "," + first + "," + second;
        boolean flag=findGroupAndPath(blockInfo,groupList,findRange, unMatched, ChangeType.update, range);
        blockInfo.setType(ChangeType.update);
        blockInfo.setFileType(changeFileType);
        if(flag){
            diffMapper.addBlock(blockInfo);
        }
    }

    public void StoreUpdateBlock(BlockInfo blockInfo,String[] range,String fileName,List<Group> groupList,
                                 Map<String, List<String>> unMatched, DiffInfo diff, Map<DiffInfo, String> unMatchedGroup){
        String before = range[0];
        int[] beforeLines = lineNumber(before);
        String after = range[1];
        int[] afterLines = lineNumber(after);
        String findRange = fileName + "," + before + "," + after;
        boolean flag=findGroupAndPath(blockInfo,groupList,findRange, unMatched, ChangeType.update, range);
        blockInfo.setType(ChangeType.update);
        if (flag){
            blockInfo.setBeginLine(afterLines[0]);
            blockInfo.setEndLine(afterLines[1]);
            blockInfo.setFileType(ChangeFileType.New);
            diffMapper.addBlock(blockInfo);

            blockInfo.setBeginLine(beforeLines[0]);
            blockInfo.setEndLine(beforeLines[1]);
            blockInfo.setFileType(ChangeFileType.Old);
            diffMapper.addBlock(blockInfo);
        }else {
            DiffInfo diff1 = new DiffInfo();
            BeanUtils.copyProperties(diff, diff1);
            unMatchedGroup.put(diff1, fileName);
        }
    }

    @Override
    public ResponseVO addCodeComment(CommentVO commentVO) {
        Comment comment=new Comment();
        BeanUtils.copyProperties(commentVO,comment);
        comment.setReviewerId(reviewMapper.getReviewerId(commentVO.getReviewId()));
        diffMapper.addComment(comment);
        return ResponseVO.buildSuccess("添加成功");
    }
    @Override
    public ResponseVO confirmGroup(Integer reviewId) {
        reviewMapper.updateReviewStatus(reviewId,ReviewStatus.WaitingPerson);
        return ResponseVO.buildSuccess();
    }


}
